package org.lemon.transport.coap.server.service.resource;

import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.Request;
import org.eclipse.californium.core.coap.Response;
import org.eclipse.californium.core.network.Exchange;
import org.eclipse.californium.core.server.resources.CoapExchange;
import org.eclipse.californium.core.server.resources.Resource;
import org.lemon.transport.context.auth.ValidateDeviceCredentialsResponse;
import org.lemon.transport.context.auth.SessionInfoCreator;
import org.lemon.transport.coap.server.service.CoapService;
import org.lemon.transport.coap.server.service.CoapTransportContext;
import org.lemon.transport.coap.server.service.callback.CoapDeviceAuthCallback;
import org.lemon.transport.context.data.device.DeviceTransportType;
import org.lemon.transport.context.data.ota.OtaPackageType;
import org.lemon.transport.context.data.security.DeviceTokenCredentials;
import org.lemon.transport.context.data.tenant.TenantId;
import org.lemon.transport.context.service.TransportService;
import org.server.transport.gen.TransportProto;
import org.lemon.transport.context.service.TransportContext;
import org.lemon.transport.context.service.TransportServiceCallback;
import org.lemon.transport.context.utils.StringUtils;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

/**
 * 获取固件包/软件包版本更新信息
 * /{OtaPackageType}/{accessToken}?title=&version=&size=&chunk=
 */
@Slf4j
public class OtaPackageTransportResource extends AbstractCoapTransportResource {

    private static final Integer ACCESS_TOKEN_POSITION = 2;

    private final OtaPackageType otaPackageType;
    private final TransportService transportService;

    public OtaPackageTransportResource(OtaPackageType otaPackageType, CoapTransportContext transportContext, CoapService coapService) {
        super(otaPackageType.getKeyPrefix(), transportContext, coapService);
        this.otaPackageType = otaPackageType;
        this.transportService = transportContext.getTransportService();
        this.setObservable(true);
    }

    @Override
    public void handleGET(CoapExchange exchange) {

        Request request = exchange.advanced().getRequest();
        log.trace("Processing {}", request);
        exchange.accept();
        Exchange advanced = exchange.advanced();
        //获取url接口凭证 /{OtaPackageType}/{accessToken}
        Optional<DeviceTokenCredentials> credentials = decodeCredentials(request);
        if (credentials.isEmpty()) {
            exchange.respond(CoAP.ResponseCode.UNAUTHORIZED);
            return;
        }

        CoapDeviceAuthCallback callback = new CoapDeviceAuthCallback(exchange, (res) -> {
            getOtaPackage(res, exchange, otaPackageType);
        });

        transportService.deviceTokenValidate(DeviceTransportType.COAP, credentials.get().getCredentialsId(), callback);
    }

    @Override
    public void handlePOST(CoapExchange exchange) {
        exchange.respond(CoAP.ResponseCode.METHOD_NOT_ALLOWED);
    }

    private void getOtaPackage(ValidateDeviceCredentialsResponse msg, CoapExchange exchange, OtaPackageType firmwareType) {

        TenantId tenantId = TenantId.valueOf(msg.getDeviceInfo().getTenantId().getValue());
        TransportProto.GetOtaPackageRequestMsg.Builder requestBuilder = TransportProto.GetOtaPackageRequestMsg.newBuilder();
        requestBuilder.setTenantId(tenantId.getValue());
        requestBuilder.setDeviceId(msg.getDeviceInfo().getDeviceId());
        requestBuilder.setType(otaPackageType.getKeyPrefix());
        transportContext.getTransportService()
                .process(SessionInfoCreator.create(msg, transportContext, UUID.randomUUID()), requestBuilder.build(),
                        new GetOtaPackageCallBack(transportContext, exchange));
    }

    private Optional<DeviceTokenCredentials> decodeCredentials(Request request) {

        List<String> uriPathList = request.getOptions().getUriPath();
        if (uriPathList.size() == ACCESS_TOKEN_POSITION) {
            String accessToken = uriPathList.get(ACCESS_TOKEN_POSITION - 1);
            return Optional.of(new DeviceTokenCredentials(accessToken));
        }
        return Optional.empty();
    }

    @Override
    public Resource getChild(String name) {
        return this;
    }

    private static class GetOtaPackageCallBack implements TransportServiceCallback<TransportProto.GetOtaPackageResponseMsg> {

        private final TransportContext transportContext;
        private final CoapExchange exchange;

        public GetOtaPackageCallBack(TransportContext transportContext, CoapExchange exchange) {
            this.transportContext = transportContext;
            this.exchange = exchange;
        }

        @Override
        public void onSuccess(TransportProto.GetOtaPackageResponseMsg msg) {

            String title = exchange.getQueryParameter("title");
            String version = exchange.getQueryParameter("version");

            if (msg.getResponseStatus().equals(TransportProto.ResponseStatus.SUCCESS)) {
                if ((title == null || msg.getTitle().equals(title)) && (version == null || msg.getVersion().equals(version))) {

                    String strChunkSize = exchange.getQueryParameter("size");
                    String strChunk = exchange.getQueryParameter("chunk");
                    int chunkSize = StringUtils.isNullOrEmpty(strChunkSize) ? 0 : Integer.parseInt(strChunkSize);
                    //从缓存中获取OtaPackage
                    Response response = new Response(CoAP.ResponseCode.CONTENT);
                    byte[] data = String.valueOf("你好").getBytes();
                    response.setPayload(data);
                    if (exchange.getRequestOptions().getBlock2() != null) {
                        int chunkSize2 = exchange.getRequestOptions().getBlock2().getSize();
                        boolean lastFlag = data.length <= chunkSize2;
                        response.getOptions().setBlock2(chunkSize2, lastFlag, 0);
                    }
                    transportContext.getExecutor().execute(() -> exchange.respond(response));
                } else {
                    exchange.respond(CoAP.ResponseCode.BAD_REQUEST);
                }
            } else {
                exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR);
            }
        }

        @Override
        public void onError(Throwable e) {
            log.warn("Failed to process request", e);
            exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR);
        }
    }
}
